/* eslint-disable @typescript-eslint/no-explicit-any */

import { Component, type StyledProps } from '@knno/jsx';
import { isDescendant } from '../../tools/utils';
import { activeListItem, findCursorElement, findParent, isCursorAtBegin } from '../editor';
import { listInputStyle } from './listInput.css';

interface ListInputProps extends StyledProps<ListInput> {
	maxLevel?: number;
	data?: unknown[];
	// serialNo?: boolean;
	readOnly?: boolean;
	disableRowAdd?: boolean;
	disableRowDelete?: boolean;
	reorderable?: boolean;
	textField?: string;
	childrenField?: string;
	suffixRender?: (row: unknown, path: number[], list: ListInput) => Node;
	onChange?: (this: ListInput) => void;
}

function getText(element: HTMLElement): string {
	return Array.from(element.childNodes)
		.filter((n) => n.nodeName === '#text')
		.map((n) => n.textContent)
		.join('');
}

function setText(element: HTMLElement, text: string): void {
	Array.from(element.childNodes)
		.filter((n) => n.nodeName === '#text')
		.forEach((n) => element.removeChild(n));
	element.insertBefore(document.createTextNode(text), element.firstChild);
}

function liContent(content: string): Node | string {
	return content ? content : <br />;
}

export class ListInput extends Component<ListInputProps, ListInput, HTMLUListElement> {
	constructor(props: ListInputProps) {
		super(props);
		this.renderContent();
	}

	render(): Node {
		return <ul contenteditable="false" class={listInputStyle} />;
	}

	activeItem(path: number[]) {
		activeListItem(this.el as HTMLUListElement, path);
	}

	private getRowIds(rows: any[], parentPath: number[]): string[] {
		let result: string[] = [];
		for (let i = 0; i < rows.length; i++) {
			result.push(parentPath.concat(i).join('-'));
			if (this.childrenField) {
				const children = rows[i][this.childrenField];
				if (children && children.length > 0) {
					result = result.concat(this.getRowIds(children, parentPath.concat(i)));
				}
			}
		}
		return result;
	}

	private activeUp(path: number[]) {
		const ids = this.getRowIds(this.data, []);
		const id = path.join('-');
		const pos = ids.indexOf(id);
		if (pos === -1) return;
		const newId = ids[pos - 1];
		if (newId) {
			this.activeItem(newId.split('-').map((s) => parseInt(s)));
		}
	}

	private activeDown(path: number[]) {
		const ids = this.getRowIds(this.data, []);
		const id = path.join('-');
		const pos = ids.indexOf(id);
		if (pos === -1) return;
		const newId = ids[pos + 1];
		if (newId) {
			this.activeItem(newId.split('-').map((s) => parseInt(s)));
		}
	}

	private moveUp(path: number[]) {
		if (path.length < 1) return;
		const idx = path.at(-1);
		if (idx === undefined) return;
		if (idx === 0) return;
		const parentPath = path.slice(0, -1);
		let parentRows: unknown[];
		if (parentPath.length > 0) {
			if (!this.childrenField) {
				return;
			}
			const parent = this.getItemByPath(parentPath);
			parentRows = parent[this.childrenField];
		} else {
			parentRows = this.data;
		}
		const removed = parentRows.splice(idx, 1);
		parentRows.splice(idx - 1, 0, ...removed);
		this.renderContent();
		this.activeItem(parentPath.concat(idx - 1));
		this.emitChange();
	}

	private moveDown(path: number[]) {
		if (path.length < 1) return;
		const idx = path.at(-1);
		if (idx === undefined) return;
		const parentPath = path.slice(0, -1);
		let parentRows: unknown[];
		if (parentPath.length > 0) {
			if (!this.childrenField) {
				return;
			}
			const parent = this.getItemByPath(parentPath);
			parentRows = parent[this.childrenField];
		} else {
			parentRows = this.data;
		}
		if (idx >= parentRows.length - 1) return;
		const removed = parentRows.splice(idx, 1);
		parentRows.splice(idx + 1, 0, ...removed);
		this.renderContent();
		this.activeItem(parentPath.concat(idx + 1));
		this.emitChange();
	}

	private getMaxRowDescendantLevel(path: number[]): number {
		const row = this.getItemByPath(path);
		const getLevel = (row: any, path: number[]): number => {
			const level = path.length;
			if (row && this.childrenField && Array.isArray(row[this.childrenField])) {
				const children = row[this.childrenField] as any[];
				return Math.max(level, ...children.map((child, i) => getLevel(child, path.concat(i))));
			} else return level;
		};
		return getLevel(row, path);
	}

	private getMaxLevel(): number {
		return this.maxLevel ?? Number.MAX_SAFE_INTEGER;
	}

	private indent(path: number[]) {
		if (path.length < 1) return;
		if (!this.childrenField) return;
		const idx = path.at(-1);
		if (idx === undefined) return;
		const maxLevel = this.getMaxLevel();
		if (this.getMaxRowDescendantLevel(path) >= maxLevel) {
			return;
		}
		if (idx > 0) {
			const item = this.getItemByPath(path);
			const previousPath = path.slice(0, -1).concat(idx - 1);
			const previousItem = this.getItemByPath(previousPath);
			const parentPath = path.slice(0, -1);
			let parentRows: unknown[];
			if (parentPath.length > 0) {
				const parent = this.getItemByPath(parentPath);
				parentRows = parent[this.childrenField];
			} else {
				parentRows = this.data;
			}
			parentRows.splice(idx, 1);
			let children = previousItem[this.childrenField];
			if (!children) {
				children = [];
				previousItem[this.childrenField] = children;
			}
			children.push(item);
			this.renderContent();
			this.activeItem(previousPath.concat(children.length - 1));
			this.emitChange();
		}
	}

	private unindent(path: number[]) {
		if (path.length <= 1) return;
		if (!this.childrenField) return;
		const idx = path.at(-1);
		if (idx === undefined) return;
		const item = this.getItemByPath(path);
		const parentPath = path.slice(0, -1);
		const parentIdx = parentPath.at(-1) as number;
		const parent = this.getItemByPath(parentPath);
		const parentRows = parent[this.childrenField] as unknown[];
		parentRows.splice(idx, 1);
		const grandPath = parentPath.slice(0, -1);
		let grandRows: unknown[];
		if (grandPath.length > 0) {
			const grandpa = this.getItemByPath(grandPath);
			grandRows = grandpa[this.childrenField];
		} else {
			grandRows = this.data;
		}
		grandRows.splice(parentIdx + 1, 0, item);
		this.renderContent();
		this.activeItem(grandPath.concat(parentIdx + 1));
		this.emitChange();
	}

	private getItemByPath(path: number[]): any {
		let row: any;
		let rows = this.data;
		for (const idx of path) {
			row = rows[idx];
			if (!row) return [];
			if (this.childrenField) {
				rows = row[this.childrenField];
			} else {
				rows = [];
			}
		}
		return row;
	}

	private getCurrentPath(): number[] | null {
		let elem = findCursorElement();
		if (!elem || !isDescendant([this.el], elem)) {
			return null;
		}
		if (elem.nodeName !== 'LI') {
			elem = findParent(elem, 'li');
		}
		if (!elem) return null;
		const path = (elem.getAttribute('path') ?? '').split(',').map((s) => parseInt(s));
		console.log(elem.getAttribute('path'));
		return path;
	}

	rowMoveUp() {
		const path = this.getCurrentPath();
		if (path) {
			this.moveUp(path);
		}
	}

	rowMoveDown() {
		const path = this.getCurrentPath();
		if (path) {
			this.moveDown(path);
		}
	}

	rowIndent() {
		const path = this.getCurrentPath();
		if (path) {
			this.indent(path);
		}
	}

	rowUnindent() {
		const path = this.getCurrentPath();
		if (path) {
			this.unindent(path);
		}
	}

	renderLis(parentRow: any | null, rows: unknown[], path: number[]) {
		const maxLevel = this.getMaxLevel();
		return rows.map((row: any, idx: number) => {
			let li: HTMLLIElement;
			return (
				<li
					path={path.concat(idx).join(',')}
					contenteditable={this.readOnly ? 'false' : 'plaintext-only'}
					onKeyDown={(evt) => {
						if (evt.key === 'Enter') {
							evt.preventDefault();
							evt.stopPropagation();
							if (this.disableRowAdd || this.readOnly) return;
							if (evt.shiftKey && !evt.ctrlKey) {
								rows.splice(idx, 0, {});
								this.renderContent();
								this.activeItem(path.concat(idx));
								this.emitChange();
							} else if (evt.ctrlKey && !evt.shiftKey) {
								if (this.childrenField && path.length + 1 < maxLevel) {
									let children = row[this.childrenField as string] as any[];
									if (!children) {
										children = [];
										row[this.childrenField as string] = children;
									}
									children.splice(0, 0, {});
									this.renderContent();
									this.activeItem(path.concat(idx, 0));
									this.emitChange();
								}
							} else {
								rows.splice(idx + 1, 0, {});
								this.renderContent();
								this.activeItem(path.concat(idx + 1));
								this.emitChange();
							}
						} else if (evt.key === 'Backspace') {
							if (this.readOnly) return;
							if (isCursorAtBegin(li)) {
								evt.stopPropagation();
								evt.preventDefault();
								if (this.disableRowDelete) return;
								if (parentRow) {
									if (rows.length > 1) {
										rows.splice(idx, 1);
										this.renderContent();
										this.activeItem(path.concat(idx - 1));
										this.emitChange();
									} else {
										delete parentRow[this.childrenField as string];
										this.renderContent();
										this.activeItem(path);
										this.emitChange();
									}
								} else if (rows.length > 1) {
									rows.splice(idx, 1);
									this.renderContent();
									this.activeItem(path.concat(idx - 1));
									this.emitChange();
								}
							}
						} else if (evt.key === 'ArrowUp') {
							evt.preventDefault();
							evt.stopPropagation();
							if (evt.ctrlKey) {
								if (this.readOnly) return;
								this.moveUp(path.concat(idx));
							} else {
								this.activeUp(path.concat(idx));
							}
						} else if (evt.key === 'ArrowDown') {
							evt.preventDefault();
							evt.stopPropagation();
							if (evt.ctrlKey) {
								if (this.readOnly) return;
								this.moveDown(path.concat(idx));
							} else {
								this.activeDown(path.concat(idx));
							}
						} else if (evt.key === 'ArrowRight' && evt.ctrlKey) {
							evt.preventDefault();
							evt.stopPropagation();
							if (this.readOnly) return;
							this.indent(path.concat(idx));
						} else if (evt.key === 'ArrowLeft' && evt.ctrlKey) {
							evt.preventDefault();
							evt.stopPropagation();
							if (this.readOnly) return;
							this.unindent(path.concat(idx));
						}
					}}
					onInput={() => {
						let txt = getText(li);
						if (txt.includes('\\n')) {
							txt = txt.replace(/\n+/g, '');
							setText(li, txt);
						}
						row[this.textField] = txt;
						this.emitChange();
					}}
					ref={(l) => {
						li = l;
					}}
				>
					{liContent(`${row[this.textField] ?? ''}`.replace(/\n+/g, ''))}
					{typeof this.props.suffixRender === 'function'
						? this.props.suffixRender(row, path.concat(idx), this)
						: ''}
					{this.childrenField && row[this.childrenField] && row[this.childrenField].length > 0 ? (
						<ul contenteditable="false">
							{this.renderLis(row, row[this.childrenField], path.concat(idx))}
						</ul>
					) : (
						''
					)}
				</li>
			);
		});
	}

	renderContent() {
		if (this.data.length < 1) {
			this.data.push({});
		}
		const data = this.data.length < 1 ? [{}] : this.data;
		this.el.replaceChildren(...this.renderLis(null, data, []));
	}

	emitChange() {
		this.el.dispatchEvent(new Event('change', { bubbles: true }));
		this.props.onChange?.call(this);
	}

	get maxLevel(): number {
		return this.props.maxLevel ?? 3;
	}
	setMaxLevel(maxLevel: number): this {
		this.props.maxLevel = maxLevel;
		this.renderContent();
		return this;
	}

	get disableRowAdd(): boolean {
		return this.props.disableRowAdd ?? false;
	}

	setDisableRowAdd(allowAdd: boolean): this {
		this.props.disableRowAdd = allowAdd;
		this.renderContent();
		return this;
	}

	get disableRowDelete(): boolean {
		return this.props.disableRowDelete ?? false;
	}
	setDisableRowDelete(allowDelete: boolean): this {
		this.props.disableRowDelete = allowDelete;
		this.renderContent();
		return this;
	}

	get reorderable(): boolean {
		return this.props.reorderable ?? false;
	}
	setReorderable(reorderable: boolean): this {
		this.props.reorderable = reorderable;
		this.renderContent();
		return this;
	}

	get readOnly(): boolean {
		return this.props.readOnly ?? false;
	}

	setReadOnly(readOnly: boolean): this {
		this.props.readOnly = readOnly;
		this.renderContent();
		return this;
	}

	get textField(): string {
		return this.props.textField ?? 'text';
	}

	setTextField(textField: string): this {
		this.props.textField = textField;
		this.renderContent();
		return this;
	}

	get childrenField(): string | undefined {
		return this.props.childrenField;
	}
	setChildrenField(childrenField: string | undefined): this {
		this.props.childrenField = childrenField;
		this.renderContent();
		return this;
	}

	get data(): unknown[] {
		if (this.props.data === undefined) {
			this.props.data = [];
		}
		return this.props.data;
	}
	setData(data: unknown[]): this {
		this.props.data = data;
		this.renderContent();
		return this;
	}

	setSuffixRender(render: (row: unknown, path: number[], list: ListInput) => Node): this {
		this.props.suffixRender = render;
		this.renderContent();
		return this;
	}
}
